/**
 * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com> and Pascal Leclercq <pascal.leclercq@gmail.com>
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.apache.poi.xwpf.converter.internal.itext;

import static org.apache.poi.xwpf.converter.internal.DxaUtil.dxa2points;
import static org.apache.poi.xwpf.converter.internal.XWPFRunUtils.getFontColor;
import static org.apache.poi.xwpf.converter.internal.XWPFRunUtils.getFontFamily;
import static org.apache.poi.xwpf.converter.internal.XWPFRunUtils.getRStyle;
import static org.apache.poi.xwpf.converter.internal.XWPFRunUtils.isBold;
import static org.apache.poi.xwpf.converter.internal.XWPFRunUtils.isItalic;
import static org.apache.poi.xwpf.converter.internal.XWPFUtils.getRPr;
import static org.apache.poi.xwpf.converter.internal.itext.XWPFTableUtil.setBorder;

import java.awt.Color;
import java.io.OutputStream;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import org.apache.poi.xwpf.converter.internal.XWPFElementVisitor;
import org.apache.poi.xwpf.converter.internal.itext.stylable.IStylableContainer;
import org.apache.poi.xwpf.converter.internal.itext.stylable.IStylableElement;
import org.apache.poi.xwpf.converter.internal.itext.stylable.StylableDocument;
import org.apache.poi.xwpf.converter.internal.itext.stylable.StylableParagraph;
import org.apache.poi.xwpf.converter.internal.itext.stylable.StylableTable;
import org.apache.poi.xwpf.converter.internal.itext.styles.Style;
import org.apache.poi.xwpf.usermodel.BodyElementType;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPicture;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFStyle;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTEmpty;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;

import com.lowagie.text.Chunk;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPCell;

import fr.opensagres.xdocreport.itext.extension.ExtendedParagraph;
import fr.opensagres.xdocreport.itext.extension.ExtendedPdfPCell;
import fr.opensagres.xdocreport.itext.extension.ExtendedPdfPTable;
import fr.opensagres.xdocreport.itext.extension.IITextContainer;
import fr.opensagres.xdocreport.itext.extension.MasterPage;
import fr.opensagres.xdocreport.itext.extension.MasterPageHeaderFooter;
import fr.opensagres.xdocreport.itext.extension.PageOrientation;
import fr.opensagres.xdocreport.utils.StringUtils;

public class PDFMapper extends XWPFElementVisitor<IITextContainer> {

	/**
	 * Logger for this class
	 */
	private static final Logger LOGGER = Logger.getLogger(XWPFElementVisitor.class.getName());

	// Create instance of PDF document
	private StylableDocument pdfDocument;
	private Stack<CTSectPr> sectPrStack = null;

	public PDFMapper(XWPFDocument document) {
		super(document);

	}

	private StyleEngineForIText styleEngine;

	@Override
	protected IITextContainer startVisitDocument(OutputStream out) throws Exception {
		// Create instance of PDF document
		styleEngine = new StyleEngineForIText(document);
		pdfDocument = new StylableDocument(out, styleEngine);
		CTSectPr sectPr = document.getDocument().getBody().getSectPr();
		applySectPr(sectPr);

		return pdfDocument;
	}

	private void applySectPr(CTSectPr sectPr) {
		if (sectPr == null) {
			return;
		}
		// Set page size
		CTPageSz pageSize = sectPr.getPgSz();
		Rectangle pdfPageSize = new Rectangle(dxa2points(pageSize.getW()), dxa2points(pageSize.getH()));
		pdfDocument.setPageSize(pdfPageSize);

		// Orientation
		org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation.Enum orientation = pageSize.getOrient();
		if (orientation != null) {
			if (org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation.LANDSCAPE.equals(orientation)) {
				pdfDocument.setOrientation(PageOrientation.Landscape);
			} else {
				pdfDocument.setOrientation(PageOrientation.Portrait);
			}
		}

		// Set page margin
		CTPageMar pageMar = sectPr.getPgMar();
		if (pageMar != null) {
			pdfDocument.setOriginalMargins(dxa2points(pageMar.getLeft()), dxa2points(pageMar.getRight()), dxa2points(pageMar.getTop()), dxa2points(pageMar.getBottom()));
		}
	}

	@Override
	protected void endVisitDocument() throws Exception {
		pdfDocument.close();
	}

	@Override
	protected void visitHeader(CTHdrFtrRef headerRef) throws Exception {
		STHdrFtr.Enum type = headerRef.getType();
		MasterPage masterPage = getOrCreateMasterPage(type);

		MasterPageHeaderFooter pdfHeader = new MasterPageHeaderFooter();
		XWPFHeader hdr = getXWPFHeader(headerRef);
		visitBodyElements(hdr.getBodyElements(), (ExtendedPdfPCell) pdfHeader.getTableCell());
		pdfHeader.flush();
		masterPage.setHeader(pdfHeader);

	}

	@Override
	protected void visitFooter(CTHdrFtrRef footerRef) throws Exception {
		STHdrFtr.Enum type = footerRef.getType();
		MasterPage masterPage = getOrCreateMasterPage(type);

		MasterPageHeaderFooter pdfFooter = new MasterPageHeaderFooter();
		XWPFFooter hdr = getXWPFFooter(footerRef);
		visitBodyElements(hdr.getBodyElements(), (ExtendedPdfPCell) pdfFooter.getTableCell());
		pdfFooter.flush();
		masterPage.setFooter(pdfFooter);

	}

	private MasterPage getOrCreateMasterPage(STHdrFtr.Enum type) {
		String masterPageName = type.toString();
		MasterPage masterPage = pdfDocument.getMasterPage(masterPageName);
		if (masterPage == null) {
			masterPage = new MasterPage(masterPageName);
			pdfDocument.addMasterPage(masterPage);
		}
		return masterPage;
	}

	private Stack<CTSectPr> getSectPrStack() {
		if (sectPrStack != null) {
			return sectPrStack;
		}
		sectPrStack = new Stack<CTSectPr>();
		for (IBodyElement bodyElement : document.getBodyElements()) {
			if (bodyElement.getElementType() == BodyElementType.PARAGRAPH) {
				CTPPr ppr = ((XWPFParagraph) bodyElement).getCTP().getPPr();
				if (ppr != null) {
					CTSectPr sectPr = ppr.getSectPr();
					if (sectPr != null) {
						sectPrStack.push(sectPr);
					}
				}
			}
		}
		return sectPrStack;
	}

	@Override
	protected IITextContainer startVisitPargraph(XWPFParagraph docxParagraph, IITextContainer parentContainer) throws Exception {

		// 1) Instanciate a pdfParagraph
		StylableParagraph pdfParagraph = pdfDocument.createParagraph((IStylableContainer) null);
		// apply style for the title font, color, bold style...
		// 2) Create style instance of the paragraph if needed
		styleEngine.startVisitPargraph(docxParagraph, pdfParagraph);
		pdfParagraph.setITextContainer(parentContainer);

		// TODO

		String backgroundColor = XWPFParagraphUtils.getBackgroundColor(docxParagraph);
		if (StringUtils.isNotEmpty(backgroundColor)) {
			pdfParagraph.getPdfPCell().setBackgroundColor(ColorRegistry.getInstance().getColor("0x" + backgroundColor));
		}
		// finally apply the style to the iText paragraph....
		applyStyles(docxParagraph, pdfParagraph);
		return pdfParagraph;
	}

	@Override
	protected void endVisitPargraph(XWPFParagraph paragraph, IITextContainer parentContainer, IITextContainer paragraphContainer) throws Exception {

		// Page Break
		// Cannot use paragraph.isPageBreak() because it throw NPE because
		// pageBreak.getVal() can be null.
		CTPPr ppr = paragraph.getCTP().getPPr();
		if (ppr.isSetPageBreakBefore()) {
			CTOnOff pageBreak = ppr.getPageBreakBefore();
			if (pageBreak != null && (pageBreak.getVal() == null || pageBreak.getVal().intValue() == STOnOff.INT_TRUE)) {
				pdfDocument.newPage();
			}
		}

		// Paragraph
		ExtendedParagraph pdfParagraph = (ExtendedParagraph) paragraphContainer;
		parentContainer.addElement(pdfParagraph.getContainer());
	}

	@Override
	protected void visitEmptyRun(IITextContainer paragraphContainer) throws Exception {
		ExtendedParagraph pdfParagraph = (ExtendedParagraph) paragraphContainer;
		pdfParagraph.add(Chunk.NEWLINE);
	}

	@Override
	protected void visitRun(XWPFRun run, IITextContainer pdfContainer) throws Exception {
		CTR ctr = run.getCTR();
		// Get family name
		// Get CTRPr from style+defaults
		CTString rStyle = getRStyle(run);
		CTRPr runRprStyle = getRPr(super.getXWPFStyle(rStyle != null ? rStyle.getVal() : null));
		CTRPr rprStyle = getRPr(super.getXWPFStyle(run.getParagraph().getStyleID()));
		CTRPr rprDefault = getRPr(defaults);

		// Font family
		String fontFamily = getFontFamily(run, rprStyle, rprDefault);

		// Get font size
		float fontSize = run.getFontSize();

		// Get font style
		int fontStyle = Font.NORMAL;
		if (isBold(run, runRprStyle, rprStyle, rprDefault)) {
			fontStyle |= Font.BOLD;
		}
		if (isItalic(run, runRprStyle, rprStyle, rprDefault)) {
			fontStyle |= Font.ITALIC;
		}

		// Process color
		Color fontColor = null;
		String hexColor = getFontColor(run, runRprStyle, rprStyle, rprDefault);
		if (StringUtils.isNotEmpty(hexColor)) {
			if (hexColor != null && !"auto".equals(hexColor)) {
				fontColor = ColorRegistry.getInstance().getColor("0x" + hexColor);
			}
		}
		// Get font
		Font font = XWPFFontRegistry.getRegistry().getFont(fontFamily, fontSize, fontStyle, fontColor);

		UnderlinePatterns underlinePatterns = run.getUnderline();

		boolean singleUnderlined = false;
		switch (underlinePatterns) {
		case SINGLE:
			singleUnderlined = true;
			break;

		default:
			break;
		}

		List<CTBr> brs = ctr.getBrList();
		for (@SuppressWarnings("unused")
		CTBr br : brs) {
			pdfContainer.addElement(Chunk.NEWLINE);
		}

		List<CTText> texts = run.getCTR().getTList();
		for (CTText ctText : texts) {

			Chunk aChunk = new Chunk(ctText.getStringValue(), font);
			if (singleUnderlined)
				aChunk.setUnderline(1, -2);

			pdfContainer.addElement(aChunk);
		}

		super.visitPictures(run, pdfContainer);

		// <w:lastRenderedPageBreak />
		List<CTEmpty> lastRenderedPageBreakList = ctr.getLastRenderedPageBreakList();
		if (lastRenderedPageBreakList != null && lastRenderedPageBreakList.size() > 0) {
			// IText Document#newPage must be called to generate page break.
			// But before that, CTSectPr must be getted to compute pageSize,
			// margins...
			// The CTSectPr <w:pPr><w:sectPr w:rsidR="00AA33F7"
			// w:rsidSect="00607077"><w:pgSz w:w="16838" w:h="11906"
			// w:orient="landscape" />...
			Stack<CTSectPr> sectPrStack = getSectPrStack();
			if (sectPrStack != null && !sectPrStack.isEmpty()) {
				CTSectPr sectPr = sectPrStack.pop();
				applySectPr(sectPr);
			}
			for (CTEmpty lastRenderedPageBreak : lastRenderedPageBreakList) {
				pdfDocument.newPage();
			}
		}
	}

	// Visit table
	protected IITextContainer startVisitTable(XWPFTable table, IITextContainer pdfContainer) throws Exception {
		styleEngine.startVisitTable(table, pdfContainer);

		// 1) Compute colWidth
		float[] colWidths = XWPFTableUtil.computeColWidths(table);

		// 2) Compute tableWith
		TableWidth tableWidth = XWPFTableUtil.getTableWidth(table);

		StylableTable pdfPTable = pdfDocument.createTable((IStylableContainer) null, colWidths.length);
		// 3) Create PDF Table.
		// ExtendedPdfPTable pdfPTable = new
		// ExtendedPdfPTable(colWidths.length);
		pdfPTable.setITextContainer(pdfContainer);

		pdfPTable.setTotalWidth(colWidths);
		if (tableWidth.width > 0) {
			if (tableWidth.percentUnit) {
				pdfPTable.setWidthPercentage(tableWidth.width);
			} else {
				pdfPTable.setTotalWidth(tableWidth.width);
			}
		}
		pdfPTable.setLockedWidth(true);
		// finally apply the style to the iText paragraph....
		applyStyles(table, pdfPTable);
		return pdfPTable;
	}

	@Override
	protected void endVisitTable(XWPFTable table, IITextContainer parentContainer, IITextContainer tableContainer) throws Exception {
		parentContainer.addElement((Element) tableContainer);
	}

	@Override
	protected IITextContainer startVisitTableCell(XWPFTableCell cell, IITextContainer tableContainer) {
		ExtendedPdfPTable pdfPTable = (ExtendedPdfPTable) tableContainer;

		XWPFTableRow row = cell.getTableRow();
		ExtendedPdfPCell pdfPCell = new ExtendedPdfPCell();
		pdfPCell.setITextContainer(pdfPTable);

		CTTcPr tcPr = cell.getCTTc().getTcPr();

		// Colspan
		Integer colspan = null;
		CTDecimalNumber gridSpan = tcPr.getGridSpan();
		if (gridSpan != null) {
			colspan = gridSpan.getVal().intValue();
		}
		if (colspan != null) {
			pdfPCell.setColspan(colspan);
		}

		// Backround Color
		CTShd shd = tcPr.getShd();
		String hexColor = null;
		if (shd != null) {
			hexColor = shd.xgetFill().getStringValue();
		}
		if (hexColor != null && !"auto".equals(hexColor)) {
			pdfPCell.setBackgroundColor(ColorRegistry.getInstance().getColor("0x" + hexColor));
		}

		// Border
		CTTcBorders borders = tcPr.getTcBorders();
		if (borders != null) {
			// border-left
			setBorder(borders.getLeft(), pdfPCell, Rectangle.LEFT);
			// border-right
			setBorder(borders.getRight(), pdfPCell, Rectangle.RIGHT);
			// border-top
			setBorder(borders.getTop(), pdfPCell, Rectangle.TOP);
			// border-bottom
			setBorder(borders.getBottom(), pdfPCell, Rectangle.BOTTOM);
		}

		int height = row.getHeight();
		pdfPCell.setMinimumHeight(dxa2points(height));

		return pdfPCell;
	}

	@Override
	protected void endVisitTableCell(XWPFTableCell cell, IITextContainer tableContainer, IITextContainer tableCellContainer) {
		ExtendedPdfPTable pdfPTable = (ExtendedPdfPTable) tableContainer;
		ExtendedPdfPCell pdfPCell = (ExtendedPdfPCell) tableCellContainer;
		pdfPTable.addCell(pdfPCell);
	}

	@Override
	protected void visitPicture(XWPFPicture picture, IITextContainer parentContainer) throws Exception {
		CTPositiveSize2D ext = picture.getCTPicture().getSpPr().getXfrm().getExt();
		long x = ext.getCx();
		long y = ext.getCy();

		CTPicture ctPic = picture.getCTPicture();
		String blipId = ctPic.getBlipFill().getBlip().getEmbed();

		XWPFPictureData pictureData = XWPFPictureUtil.getPictureData(document, blipId);

		if (pictureData != null) {
			try {
				Image img = Image.getInstance(pictureData.getData());
				img.scaleAbsolute(dxa2points(x) / 635, dxa2points(y) / 635);

				IITextContainer parentOfParentContainer = parentContainer.getITextContainer();
				if (parentOfParentContainer != null && parentOfParentContainer instanceof PdfPCell) {
					((PdfPCell) parentOfParentContainer).setImage(img);
				} else {
					parentContainer.addElement(img);
				}

			} catch (Exception e) {
				LOGGER.severe(e.getMessage());
			}

		}

	}

	private void applyStyles(XWPFParagraph ele, IStylableElement<XWPFParagraph> element) {

		Style style = styleEngine.getStyle(ele.getStyleID());

		element.applyStyles(ele, style);

	}

	private void applyStyles(XWPFTable ele, IStylableElement<XWPFTable> element) {

		CTString tblStyle = ele.getCTTbl().getTblPr().getTblStyle();
		Style style;
		if (tblStyle != null) {
			style = styleEngine.getStyle(tblStyle.getVal());
		} else {
			style = styleEngine.getDefaultStyle();
		}
		element.applyStyles(ele, style);

	}

	protected XWPFStyle getXWPFStyle(XWPFParagraph paragraph) {
		if (paragraph == null) {
			return null;
		}
		return getXWPFStyle(paragraph.getStyleID());
	}
}
